package com.changanw.middleware.mybatis;

import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.support.ResourcePatternResolver;
import org.xml.sax.InputSource;

import java.io.InputStreamReader;
import java.io.Reader;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @author changanw
 * @description
 * 在这个类中包括的核心方法有；
 * build(构建实例化元素)、
 * parseConfiguration(解析配置)、
 * dataSource(获取数据库配置)、
 * connection(Map dataSource) (链接数据库)、
 * mapperElement (解析sql语句)
 * @date 2024/2/27 15:32
 */
public class SqlSessionFactoryBuilder {
  /**
   * 构建实例化元素
   * 主要用于创建解析xml文件的类，以及初始化SqlSession工厂类DefaultSqlSessionFactory。
   * 另外需要注意这段代码saxReader.setEntityResolver(new XMLMapperEntityResolver());，
   * 是为了保证在不联网的时候一样可以解析xml，否则会需要从互联网获取dtd文件。
   * @param reader
   * @return
   */
  public DefaultSqlSessionFactory build(Reader reader) {
    SAXReader saxReader = new SAXReader();
    try {
      saxReader.setEntityResolver(new XMLMapperEntityResolver());
      Document document = saxReader.read(new InputSource(reader));
      Configuration configuration = parseConfiguration(document.getRootElement());
      return new DefaultSqlSessionFactory(configuration);
    } catch (DocumentException e) {
      e.printStackTrace();
    }
    return null;
  }

  /**
   * starter 使用的 build 方法 传参信息提供了数据库实例 Connection 和 packageSearchPath 的扫描路径。
   *
   * @param connection
   * @param packageSearchPath packageSearchPath 就是 classpath*:mapper/*.xml 的路径信息，
   *                          这不过这里的处理方式是交给 ResourcePatternResolver 类处理，获取 InputStream。
   * @return
   * @throws Exception
   */
  public DefaultSqlSessionFactory build(Connection connection, String packageSearchPath) throws Exception {
    Configuration configuration = new Configuration();
    configuration.setConnection(connection);
    // 读取配置
    ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver();
    Resource[] resources = resourcePatternResolver.getResources(packageSearchPath);
    List<Element> list = new ArrayList<>(resources.length);
    SAXReader saxReader = new SAXReader();
    //放在没有网络的时候导致解析xml失败
    saxReader.setEntityResolver(new XMLMapperEntityResolver());
    for (Resource resource : resources) {
      Document document = saxReader.read(new InputSource(new InputStreamReader(resource.getInputStream())));
      list.add(document.getRootElement());
    }
    configuration.setMapperElement(mapperElement(list));
    return new DefaultSqlSessionFactory(configuration);
  }

  /**
   * 解析配置文件
   * 是对xml中的元素进行获取，这里主要获取了；
   * dataSource、mappers，而这两个配置一个是我们数据库的链接信息，另外一个是对数据库操作语句的解析。
   * @param root
   * @return
   */
  private Configuration parseConfiguration(Element root) {
    Configuration configuration = new Configuration();
    configuration.setDataSource(dataSource(root.selectNodes("//dataSource")));
    configuration.setConnection(connection(configuration.dataSource));
    configuration.setMapperElement(mapperElement(root.selectNodes("mappers")));
    return configuration;
  }
  // 获取数据源配置信息
  private Map<String, String> dataSource(List<Element> list) {
    Map<String, String> dataSource = new HashMap<>(4);
    Element element = list.get(0);
    List content = element.content();
    for (Object o : content) {
      Element e = (Element) o;
      String name = e.attributeValue("name");
      String value = e.attributeValue("value");
      dataSource.put(name, value);
    }
    return dataSource;
  }

  /**
   * 链接数据库的地方和我们常见的方式是一样的；Class.forName(dataSource.get("driver"));，
   * 但是这样包装以后外部是不需要知道具体的操作。同时当我们需要链接多套数据库的时候，也是可以在这里扩展。
   * @param dataSource
   * @return
   */
  private Connection connection(Map<String, String> dataSource) {
    try {
      Class.forName(dataSource.get("driver"));
      return DriverManager.getConnection(dataSource.get("url"), dataSource.get("username"), dataSource.get("password"));
    } catch (ClassNotFoundException | SQLException e) {
      e.printStackTrace();
    }
    return null;
  }
  // 获取SQL语句信息

  /**
   * 这部分代码块内容相对来说比较长，但是核心的点就是为了解析xml中的sql语句配置。
   * 在我们平常的使用中基本都会配置一些sql语句，也有一些入参的占位符。在这里我们使用正则表达式的方式进行解析操作
   * @param list
   * @return
   */
  private Map<String, XNode> mapperElement(List<Element> list) {
    Map<String, XNode> map = new HashMap<>();
    //此处 是 mybatis-spring-start 解析xml的方式
    for (Element root : list) {
      //命名空间
      String namespace = root.attributeValue("namespace");
      // SELECT
      List<Element> selectNodes = root.selectNodes("select");
      for (Element node : selectNodes) {
        String id = node.attributeValue("id");
        String parameterType = node.attributeValue("parameterType");
        String resultType = node.attributeValue("resultType");
        String sql = node.getText();

        // ? 匹配
        Map<Integer, String> parameter = new HashMap<>();
        Pattern pattern = Pattern.compile("(#\\{(.*?)})");
        Matcher matcher = pattern.matcher(sql);
        for (int i = 1; matcher.find(); i++) {
          String g1 = matcher.group(1);
          String g2 = matcher.group(2);
          parameter.put(i, g2);
          sql = sql.replace(g1, "?");
        }

        XNode xNode = new XNode();
        xNode.setNamespace(namespace);
        xNode.setId(id);
        xNode.setParameterType(parameterType);
        xNode.setResultType(resultType);
        xNode.setSql(sql);
        xNode.setParameter(parameter);

        map.put(namespace + "." + id, xNode);
      }
    }
    //下面是原始的 orm框架解析xml的过程
    /*Element element = list.get(0);
    List content = element.content();
    for (Object o : content) {
      Element e = (Element) o;
      String resource = e.attributeValue("resource");

      try {
        Reader reader = Resources.getResourceAsReader(resource);
        SAXReader saxReader = new SAXReader();
        //此行的目的是 为了保证在没有网的情况下 也能够解析xml
        saxReader.setEntityResolver(new XMLMapperEntityResolver());
        Document document = saxReader.read(new InputSource(reader));
        Element root = document.getRootElement();
        //命名空间
        String namespace = root.attributeValue("namespace");

        // SELECT
        List<Element> selectNodes = root.selectNodes("select");
        for (Element node : selectNodes) {
          String id = node.attributeValue("id");
          String parameterType = node.attributeValue("parameterType");
          String resultType = node.attributeValue("resultType");
          String sql = node.getText();

          // ? 匹配
          Map<Integer, String> parameter = new HashMap<>();
          Pattern pattern = Pattern.compile("(#\\{(.*?)})");
          Matcher matcher = pattern.matcher(sql);
          for (int i = 1; matcher.find(); i++) {
            String g1 = matcher.group(1);
            String g2 = matcher.group(2);
            parameter.put(i, g2);
            sql = sql.replace(g1, "?");
          }

          XNode xNode = new XNode();
          xNode.setNamespace(namespace);
          xNode.setId(id);
          xNode.setParameterType(parameterType);
          xNode.setResultType(resultType);
          xNode.setSql(sql);
          xNode.setParameter(parameter);

          map.put(namespace + "." + id, xNode);
        }
      } catch (Exception ex) {
        ex.printStackTrace();
      }

    }*/
    return map;
  }
}
